iT邦幫忙

2022 iThome 鐵人賽

DAY 4
0
Modern Web

Rails,我要進來囉系列 第 4

第四天:Ruby + ActiveSupport = Ruby 穿全身+9神裝!

  • 分享至 

  • xImage
  •  

開場白

鼬~~哩賀,我是寫程式的山姆老弟,今天來一探究竟 ActiveSupport 這個非常常見的 gem,到底在 Rails 中扮演什麼樣的角色!

Active Support Core Extensions - Ruby on Rails Guides

Ruby + ActiveSupport = Ruby 穿了全套 +9 發光裝備

為什麼這樣說呢,是因為 ActiveSupport 對於原生的 Ruby 做了很多擴充,就像是 Ruby 穿了神裝一樣,變得更強大、變得更多功能,也就是很多人對 Rails 說的 magic,我想 ActiveSupport 應該是蠻大的功臣,我們接下去看一下 ActiveSupport 做了哪些擴充吧~

相信各位 Rails 開發者的你各位,都有用過 .present? 這東東,用來判斷物件是不是存在的 method,在原生 Ruby 是沒有這個 method 的,原生的 Ruby 只有 .nil?,來判斷 Object 是不是空的的判斷方式

.present? 就是 ActiveSupport 幫所有物件新增的一個 magic method 的其中之一,其他 magic methods 還有:

  1. blank?
  2. presence
  3. duplicable?
  4. deep_dup
  5. try
  6. class_eval(*args, &block)
  7. acts_like?(duck)
  8. to_param
  9. to_query
  10. with_options

以上是 ActiveSupport 針對 Object 這個 Class 所做的擴充,也就是對所有物件都適用,除了對 Object 擴充之外,還有對 Module、對 Class、對 String、對 Symbol、對 Numeric、對 Integer、對 BigDecimal、對 Enumerable、對 Array、對 Hash、對 Regexp、對 Range、對 Date、對 DateTime、對 Time、對 File、對 NameError、對 LoadError、對 Pathname,沒錯,對超多 Class 擴充,總之就是很大一包

ActiveSupport 的內容很多XD,這一篇先將 ActiveSupport 擴充 Ruby 的部分列出,再加上官方有提供範例、同時覺得有助於理解的,就會把範例一起寫下來

適用 All Object

  1. blank?

  2. present?

  3. presence

    host = config[:host].presence || 'localhost'
    
  4. duplicable?

  5. deep_dup

  6. try

  7. class_eval

  8. acts_like?

  9. to_param

    7.to_param # => "7"
    "Tom & Jerry".to_param # => "Tom & Jerry"
    [0, true, String].to_param # => "0/true/String"
    
  10. to_query

    [3.4, -45.6].to_query('sample') # => "sample%5B%5D=3.4&sample%5B%5D=-45.6"
    {c: 3, b: 2, a: 1}.to_query # => "a=1&b=2&c=3"
    {id: 89, name: "John Smith"}.to_query('user') # => "user%5Bid%5D=89&user%5Bname%5D=John+Smith"
    
  11. with_options

  12. instance_values

  13. instance_variable_names

  14. in?

適用 Module

  1. alias_attribute
  2. attr_internal_reader, attr_internal_writer, and attr_internal_accessor
  3. mattr_reader, mattr_writer, and mattr_accessor
  4. module_parent
  5. module_parent_name
  6. module_parents
  7. anonymous?
  8. delegate
  9. delegate_missing_to
  10. redefine_method

適用 Class

  1. class_attribute
  2. cattr_reader, cattr_writer, and cattr_accessor
  3. subclasses
  4. descendants

適用 String

  1. html_safe, html_safe?, raw

  2. remove

  3. squish

    " \n  foo\n\r \t bar \n".squish # => "foo bar"
    
  4. truncate, truncate_bytes, truncate_words

    "Oh dear! Oh dear! I shall be late!".truncate(20) # => "Oh dear! Oh dear!..."
    "Oh dear! Oh dear! I shall be late!".truncate(20, omission: ' ...more') # "Oh dear! Oh  ...more"
    
    "Oh dear! Oh dear! I shall be late!".truncate(18) # => "Oh dear! Oh dea..."
    "Oh dear! Oh dear! I shall be late!".truncate(18, separator: ' ') # => "Oh dear! Oh..."
    
  5. inquiry

    "production".inquiry.production? # => true
    "active".inquiry.inactive?       # => false
    
  6. starts_with?, ends_with?

  7. strip_heredoc

    if options[:usage]
      puts <<-USAGE.strip_heredoc
        This command does such and such.
    
        Supported options are:
          -h         This message
          ...
      USAGE
    end
    
  8. indent

    puts <<EOS.indent(2)
    def some_method
      some_code
    end
    EOS
    
  9. at, from, to

  10. first, last

  11. pluralize, singularize

    "table".pluralize     # => "tables"
    "ruby".pluralize      # => "rubies"
    "equipment".pluralize # => "equipment"
    
    "dude".pluralize(0) # => "dudes"
    "dude".pluralize(1) # => "dude"
    "dude".pluralize(2) # => "dudes"
    
    "tables".singularize    # => "table"
    "rubies".singularize    # => "ruby"
    "equipment".singularize # => "equipment"
    
  12. camelize / camelcase, underscore

    "admin_user".camelize # => "AdminUser"
    "backoffice/session".camelize # => "Backoffice::Session"
    "visual_effect".camelize(:lower) # => "visualEffect"
    
    "AdminUser".underscore # => "admin_user"
    "Backoffice::Session".underscore # => "backoffice/session"
    "visualEffect".underscore # => "visual_effect"
    
  13. titleize / titlecase, dasherize

    "alice in wonderland".titleize # => "Alice In Wonderland"
    "fermat's enigma".titleize     # => "Fermat's Enigma"
    
    "name".dasherize         # => "name"
    "contact_data".dasherize # => "contact-data"
    
  14. demodulize, deconstantize

    "Product".demodulize                        # => "Product"
    "Backoffice::UsersController".demodulize    # => "UsersController"
    "Admin::Hotel::ReservationUtils".demodulize # => "ReservationUtils"
    "::Inflections".demodulize                  # => "Inflections"
    "".demodulize                               # => ""
    
    "Product".deconstantize                        # => ""
    "Backoffice::UsersController".deconstantize    # => "Backoffice"
    "Admin::Hotel::ReservationUtils".deconstantize # => "Admin::Hotel"
    
  15. parameterize

    "John Smith".parameterize # => "john-smith"
    "John Smith".parameterize(preserve_case: true) # => "John-Smith"
    "John Smith".parameterize(separator: "_") # => "john\_smith"
    
  16. tableize = pluralize + underscore, classify

    "Person".tableize      # => "people"
    "Invoice".tableize     # => "invoices"
    "InvoiceLine".tableize # => "invoice_lines"
    
    "people".classify        # => "Person"
    "invoices".classify      # => "Invoice"
    "invoice_lines".classify # => "InvoiceLine"
    "highrise_production.companies".classify # => "Company"
    
  17. constantize

    "Integer".constantize # => Integer
    "String".constantize # => String
    
  18. humanize

    "name".humanize                         # => "Name"
    "author_id".humanize                    # => "Author"
    "author_id".humanize(capitalize: false) # => "author"
    "comments_count".humanize               # => "Comments count"
    "_id".humanize                          # => "Id"
    
  19. foreign_key

    "User".foreign_key           # => "user_id"
    "InvoiceLine".foreign_key    # => "invoice_line_id"
    "Admin::Session".foreign_key # => "session_id"
    
  20. to_date, to_time, to_datetime

    "2010-07-27".to_date              # => Tue, 27 Jul 2010
    "2010-07-27 23:37:00".to_time     # => 2010-07-27 23:37:00 +0200
    "2010-07-27 23:37:00".to_datetime # => Tue, 27 Jul 2010 23:37:00 +0000
    

    還有發現一個酷東西(注意:這不是 ActiveSupport 的內容)

    DateTime.parse("2010-07-27 23:42:00")  # => Tue, 27 Jul 2010 23:42:00 +0000
    DateTime._parse("2010-07-27 23:42:00") # => {:hour=>23, :min=>42, :sec=>0, :year=>2010, :mon=>7, :mday=>27}
    
    Date.parse("2010-07-27 23:42:00")      # => Tue, 27 Jul 2010
    Date._parse("2010-07-27 23:42:00")     # => {:hour=>23, :min=>42, :sec=>0, :year=>2010, :mon=>7, :mday=>27}
    

適用於 Symbol

  1. starts_with?, ends_with?

適用於 Numeric

  1. bytes, kilobytes, megabytes, gigabytes, terabytes, petabytes, exabytes

    2.kilobytes   # => 2048
    3.megabytes   # => 3145728
    3.5.gigabytes # => 3758096384
    -4.exabytes   # => -4611686018427387904
    
  2. seconds, minutes, hours, days, weeks, fortnights

    # equivalent to Time.current.advance(days: 1)
    1.day.from_now
    
    # equivalent to Time.current.advance(weeks: 2)
    2.weeks.from_now
    
    # equivalent to Time.current.advance(days: 4, weeks: 5)
    (4.days + 5.weeks).from_now
    
  3. to_fs (rails 7 才有哦,正確來說是 ActiveSupport 7.x 版本以上才有,6.x 版本以下叫做 to_s)

    100.to_fs(:percentage, precision: 0)           # => 100%
    1234567890.506.to_fs(:currency, precision: 3)  # => $1,234,567,890.506 可惜只有美金
    12345678.05.to_fs(:delimited)                  # => 12,345,678.05
    111.2345.to_fs(:rounded, precision: 2)         # => 111.23
    1234567890.to_fs(:human_size)                  # => 1.15 GB
    1234567890.to_fs(:human)                       # => "1.23 Billion"
    

適用於 Integer

  1. multiple_of?

    2.multiple_of?(1) # => true
    1.multiple_of?(2) # => false
    
  2. ordinal, ordinalize

    1.ordinal    # => "st"
    2.ordinal    # => "nd"
    53.ordinal   # => "rd"
    2009.ordinal # => "th"
    
    1.ordinalize    # => "1st"
    2.ordinalize    # => "2nd"
    53.ordinalize   # => "53rd"
    2009.ordinalize # => "2009th"
    

適用於 BigDecimal

  1. to_s

適用於 Enumerable

  1. sum

    [1, 2, 3].sum                   # => 6
    (1..100).sum                    # => 5050
    [[1, 2], [2, 3], [3, 4]].sum    # => [1, 2, 2, 3, 3, 4]
    %w(foo bar baz).sum             # => "foobarbaz"
    {a: 1, b: 2, c: 3}.sum          # => [:a, 1, :b, 2, :c, 3]
    (1..5).sum { |n| n * 2 }         # => 30
    [2].sum(10) { |n| n**3 }          # => 18
    
  2. index_by

    invoices.index_by(&:number)
    # => {'2009-032' => <Invoice ...>, '2009-008' => <Invoice ...>, ...}
    
  3. index_with

    post = Post.new(title: "hey there", body: "what's up?")
    
    %i( title body ).index_with { |attr_name| post.public_send(attr_name) } # => { title: "hey there", body: "what's up?" }
    
  4. many?

    <% if pages.many? %>
      <%= pagination_links %>
    <% end %>
    
    @see_more = videos.many? {|video| video.category == params[:category]}
    
  5. exclude?

  6. including, excluding

  7. pluck

  8. pick

適用於 Array

  1. to, from, including, excluding, second, third, fourth, fifth, second_to_last, third_to_last, 還有個神奇的 fourty_two (是個科幻梗,來自 銀河便車指南,代表宇宙一切的答案)

    其實 ActiveSupport 大部分的 magic method 的實作都不難理解

    其實 ActiveSupport 大部分的 magic method 的實作都不難理解

  2. extract!

    numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
    odd_numbers = numbers.extract! { |number| number.odd? } # => [1, 3, 5, 7, 9]
    numbers # => [0, 2, 4, 6, 8]
    
  3. extract_options!

  4. to_sentence

  5. to_fs

  6. to_xml

  7. Array.wrap

  8. deep_dup

  9. in_groups_of, in_groups

    [1, 2, 3].in_groups_of(2) # => [[1, 2], [3, nil]]
    [1, 2, 3].in_groups_of(2, 0) # => [[1, 2], [3, 0]]
    [1, 2, 3].in_groups_of(2, false) # => [[1, 2], [3]]
    
    [1, 2, 3, 4, 5, 6, 7].in_groups(3) # => [["1", "2", "3"], ["4", "5", nil], ["6", "7", nil]]
    [1, 2, 3, 4, 5, 6, 7].in_groups(3, "0") # => [["1", "2", "3"], ["4", "5", "0"], ["6", "7", "0"]]
    [1, 2, 3, 4, 5, 6, 7].in_groups(3, false) # => [["1", "2", "3"], ["4", "5"], ["6", "7"]]
    
    <% sample.in_groups_of(3) do |a, b, c| %>
      <tr>
        <td><%= a %></td>
        <td><%= b %></td>
        <td><%= c %></td>
      </tr>
    <% end %>
    
    
  10. split

    [0, 1, -5, 1, 1, "foo", "bar"].split(1) # => [[0], [-5], [], ["foo", "bar"]]
    
    (-5..5).to_a.split { |i| i.multiple_of?(4) } # => [[-5], [-3, -2, -1], [1, 2, 3], [5]]
    

適用於 Hash

  1. to_xml

  2. reverse_merge, reverse_merge!

    options = {a:1, b:2, c:3}
    options.merge(a: 5)          # => {:a=>5, :b=>2, :c=>3}
    options.reverse_merge(a: 5)  # => {:a=>1, :b=>2, :c=>3}
    
    
  3. reverse_update = reverse_merge!

  4. deep_merge, deep_merge!

    {a: {b: 1}}.deep_merge(a: {c: 2})  # => {:a=>{:b=>1, :c=>2}}
    
  5. deep_dup

  6. except, except!

  7. stringify_keys, stringify_keys!, deep_stringify_keys, deep_stringify_keys!

  8. symbolize_keys, symbolize_keys!, deep_symbolize_keys, deep_symbolize_keys!

  9. to_options = symbolize_keys, to_options!

  10. asset_valid_keys

  11. deep_transform_values, deep_transform_values!

    hash = { person: { name: 'Rob', age: '28' } }
    
    hash.deep_transform_values{ |value| value.to_s.upcase }  # => {person: {name: "ROB", age: "28"}}
    
  12. slice!, extract!

    hash = {a: 1, b: 2, c: 3}
    rest = hash.slice!(:a)    # => {:b=>2, :c=>3}
    hash                      # => {:a=>1}
    
    hash = {a: 1, b: 2, c: 3}
    rest = hash.extract!(:a)  # => {:a=>1}
    hash                      # => {:b=>2, :c=>3}
    
  13. with_indifferent_access

    {a: 1}.with_indifferent_access["a"] # => 1
    

適用於 Regexp

  1. multiline?

適用於 Range

  1. to_s

    (Date.today..Date.tomorrow).to_s       # => "2009-10-25..2009-10-26"
    (Date.today..Date.tomorrow).to_s(:db)  # => "BETWEEN '2009-10-25' AND '2009-10-26'"
    
  2. === , include?

    (1..10) === (3..7)  # => true
    (1...9) === (3..9)  # => false
    
    (1..10).include?(3..7)  # => true
    (1...9).include?(3..9)  # => false
    
  3. overlaps?

    (1..10).overlaps?(7..11)  # => true
    (1...10).overlaps?(10..11)  # => false
    

適用於 Date

  1. Date.current (有時區; Time.now 是沒時區的)

  2. beginning_of_week/at_beginning_of_week, end_of_week/at_end_of_week

    d = Date.new(2010, 5, 8)     # => Sat, 08 May 2010
    d.beginning_of_week          # => Mon, 03 May 2010
    d.beginning_of_week(:sunday) # => Sun, 02 May 2010
    d.end_of_week                # => Sun, 09 May 2010
    d.end_of_week(:sunday)       # => Sat, 08 May 2010
    
  3. monday, sunday

  4. prev_week/last_week , next_week

    d = Date.new(2010, 5, 9) # => Sun, 09 May 2010
    d.next_week              # => Mon, 10 May 2010
    d.next_week(:saturday)   # => Sat, 15 May 2010
    d.prev_week              # => Mon, 26 Apr 2010
    d.prev_week(:saturday)   # => Sat, 01 May 2010
    d.prev_week(:friday)     # => Fri, 30 Apr 2010
    
  5. begining_of_month/at_beginning_of_month, end_of_month/ at_end_of_month, beginning_of_quarter/at_beginning_of_quarter, end_of_quarter/at_end_of_quarter, beginning_of_year/at_beginning_of_year, end_of_year / at_end_of_year, beginning_of_day / at_beginning_of_day, end_of_day / at_end_of_day, midnight/at_mid_night, beginning_of_hour/ at_beginning_of_hour, end_of_hour / at_end_of_hour, beginning_of_minute / at_beginning_of_minute, end_of_minute / at_end_of_minute

  6. years_ago, years_since, months_ago, months_since, weeks_ago

  7. advance, change

    date = Date.new(2010, 6, 6)
    date.advance(years: 1, weeks: 2)                      # => Mon, 20 Jun 2011
    date.advance(months: 2, days: -2)                     # => Wed, 04 Aug 2010
    
    Date.new(2010, 12, 23).change(year: 2011, month: 11)  # => Wed, 23 Nov 2011
    Date.new(2010, 1, 31).change(month: 2)                # => ArgumentError: invalid date
    
  8. ago, since

    date = Date.current # => Fri, 11 Jun 2010
    date.ago(1)         # => Thu, 10 Jun 2010 23:59:59 EDT -04:00
    
    date = Date.current # => Fri, 11 Jun 2010
    date.since(1)       # => Fri, 11 Jun 2010 00:00:01 EDT -04:00
    

適用於 DateTime

  1. DateTime.current

  2. seconds_since_midnight

    now = DateTime.current     # => Mon, 07 Jun 2010 20:26:36 +0000
    now.seconds_since_midnight # => 73596
    
  3. utc/get_utc, utc?

    now = DateTime.current # => Mon, 07 Jun 2010 19:27:52 -0400
    now.utc                # => Mon, 07 Jun 2010 23:27:52 +0000
    
  4. advance, change

    d = DateTime.current  # => Thu, 05 Aug 2010 11:33:31 +0000
    d.advance(years: 1, months: 1, days: 1, hours: 1, minutes: 1, seconds: 1)  # => Tue, 06 Sep 2011 12:34:32 +0000
    

適用於 Time

  1. Time.current
  2. advance, change
  3. all_day, all_week, all_month, all_quarter, all_year
  4. prev_day, next_day, prev_month, next_month, prev_quarter, next_quarter, prev_year, next_year

適用於 File

  1. atomic_write

    File.atomic_write(joined_asset_path) do |cache|
      cache.write(join_asset_file_contents(asset_paths))
    end
    

適用於 NameError, LoadError

  1. missing_name?, is_missing?

    def default_helper_module!
      module_name = name.delete_suffix("Controller")
      module_path = module_name.underscore
      helper module_path
    rescue LoadError => e
      raise e unless e.is_missing? "helpers/#{module_path}_helper"
    rescue NameError => e
      raise e unless e.missing_name? "#{module_name}Helper"
    end
    

適用於 Pathname

  1. existence (Rails7 才有)

    content = Pathname.new("file").existence&.read
    

總結

ActiveSupport 真的超多東西,看完我也嚇一跳,有些 magic method 我看了一下 source code 之後,覺得確實有包成 magic method 會是比較好一點,就不需要自己手刻這些很方便、卻又很簡單的功能,明天會跟大家來一探 ActiveSupport 某些我覺得比較有印象的 source code,我們明天見~


上一篇
第三天:為什麼 Rails 不需要常常使用 require?Rails 的 autoloading 是什麼魔法?
下一篇
第五天:稍微深入 ActiveSupport 一點點,一起來看點 source code
系列文
Rails,我要進來囉30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言